using System;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.Phone.Scheduler;

namespace LazyMapper.Core.Helpers
{
    /// <summary>
    /// Provides extensions for converting lambda functions into assignment actions
    /// </summary>
    public static class ExpressionExtensions
    {
        /// <summary>
        /// Converts a field/property retrieve expression into a field/property assign expression
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TProp">The type of the prop.</typeparam>
        /// <param name="fieldGetter">The field getter.</param>
        /// <returns></returns>
        public static Expression<Action<TInstance, TProp>> ToFieldAssignExpression<TInstance, TProp>(
            this Expression<Func<TInstance, TProp>> fieldGetter)
        {
            if (fieldGetter == null)
                throw new ArgumentNullException("fieldGetter");

            if (fieldGetter.Parameters.Count != 1 || !(fieldGetter.Body is MemberExpression))
                throw new ArgumentException(
                    @"Input expression must be a single parameter field getter, e.g. g => g._fieldToSet  or function(g) g._fieldToSet");

            var parms = new[]
                            {
                                fieldGetter.Parameters[0],
                                Expression.Parameter(typeof (TProp), "value")
                            };

            Expression body = Expression.Call(AssignmentHelper<TProp>.MethodInfoSetValue,
                                              new[] {fieldGetter.Body, parms[1]});

            return Expression.Lambda<Action<TInstance, TProp>>(body, parms);
        }


        public static Action<TInstance, TProp> ToFieldAssignment<TInstance, TProp>(this Expression<Func<TInstance, TProp>> fieldGetter)
        {
            return fieldGetter.ToFieldAssignExpression().Compile();
        }

        #region Nested type: AssignmentHelper

        private class AssignmentHelper<T>
        {
            internal static readonly MethodInfo MethodInfoSetValue =
                typeof (AssignmentHelper<T>).GetMethod("SetValue", BindingFlags.NonPublic | BindingFlags.Static);

            private static void SetValue(ref T target, T value)
            {
                target = value;
            }
        }

        #endregion

        public static Action<TInput, TOutput> CopyValue<TInput, TOutput>(PropertyInfo inputProp, PropertyInfo outputProp) 
        {
            // create [output.Value = input.Value]
            var parms = new[]
                            {
                                Expression.Parameter(typeof (TInput), "input"),
                                Expression.Parameter(typeof (TOutput), "output"),
                            };
            var propertyType = inputProp.PropertyType;
            Type constructed = typeof(AssignmentHelper<>).MakeGenericType(propertyType);
            var methodInfo = constructed.GetMethod("SetValue", BindingFlags.NonPublic | BindingFlags.Static);
            

            MethodCallExpression methodCall = Expression.Call(
                methodInfo,
                Expression.Property(parms[1], outputProp.Name),
                Expression.Property(parms[0], inputProp.Name)
            );
            
            //MethodCallExpression methodCall = Expression.Call(objectPar, xPropSetter, newValPar);
            
            var expression = Expression.Lambda<Action<TInput, TOutput>>(methodCall, parms);
            return expression.Compile();

           

        }
    }
}